﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  internal class GroupContextEnumerator : AsyncParentEnumerator<DataPath>
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal GroupContextEnumerator( IGroupEnumerator childEnumerator, IContextualGroupHelper groupHelper )
      : base( childEnumerator )
    {
      if( groupHelper == null )
        throw new ArgumentNullException( "groupHelper" );

      m_groupHelper = groupHelper;
    }

    #endregion

    #region GroupEnumerator Private Property

    private IGroupEnumerator GroupEnumerator
    {
      get
      {
        return ( IGroupEnumerator )this.ChildEnumerator;
      }
    }

    #endregion

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult( GroupContextEnumerator.JumpMoveAndFetchCallId, userCallback, asyncState, parameters );

      DataSourceContext context = parameters.Context;

      asyncResult.Start(
        ( cb, st ) => this.BaseBeginJumpMoveAndFetch( parameters, cb, st ),
        ( par, iar ) => this.HandleGroupContexts( context, this.BaseEndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar ) ) );

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<DataPath> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( GroupContextEnumerator.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<DataPath>>();
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      base.Dispose( disposing );
    }

    private DependentCallResult HandleGroupContexts(
      DataSourceContext context,
      JumpMoveAndFetchResult<DataPath> result )
    {
      if( ( m_disposed ) || ( result == null ) )
        return new DependentCallResult( null );

      if( ( result.FetchedItems == null ) || ( result.FetchedItems.Count == 0 ) )
        return new DependentCallResult( result );

      IEnumerator<DataPath> fetchedItemsEnumerator = result.FetchedItems.GetEnumerator();

      if( !fetchedItemsEnumerator.MoveNext() )
      { 
        Debug.Assert( false, "MoveNext not working" );
        return new DependentCallResult( null );
      }

      return this.HandleGroupContextsCallback( context, result, fetchedItemsEnumerator, 0 );
    }

    private DependentCallResult HandleGroupContextsCallback(
      DataSourceContext context,
      JumpMoveAndFetchResult<DataPath> result,
      IEnumerator<DataPath> fetchedItems,
      int currentDepth )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      DataSourceGroupContext groupContext = context.GroupContext;

      IGroupStatFunctionsSelector statFunctionsSelector = groupContext.GroupStatFunctionsSelector;
      bool includeCount = groupContext.IncludeGroupItemsCount;
      bool includeItems = groupContext.IncludeGroupItems;

      // There is nothing to do if there is no selector.
      if( ( statFunctionsSelector == null ) && ( !includeCount ) && ( !includeItems ) )
        return new DependentCallResult( result );

      DataPath groupPath;
      IEnumerable<StatFunction> statFunctions = null;

      do
      {
        // There is no more group to verify.
        if( !this.MoveToNextGroup( ref fetchedItems, ref currentDepth ) )
          return new DependentCallResult( result );

        groupPath = fetchedItems.Current.CreateAncestorOrSamePath( currentDepth );

        // Retrieve the active stat functions for the group.
        if( statFunctionsSelector != null )
        {
          statFunctions = statFunctionsSelector.GetStatFunctions( groupPath );
        }
      }
      while( ( !includeCount ) && ( !includeItems ) && ( statFunctions == null || !statFunctions.Any() ) );

      GroupDataItem dataItem = ( GroupDataItem )groupPath.LastChild;
      List<DependentCallResult> contextCallsList = new List<DependentCallResult>();
      IEnumerator<DependentCallResult> contextCalls = null;

      Func<AsyncCallback, object, IAsyncResult> includeItemsCall = null;
      Func<AsyncResult, IAsyncResult, DependentCallResult> includeItemsCallback = null;

      if( includeCount || includeItems )
      {
        contextCallsList.Add(
          new DependentCallResult(
            null,
            ( cb, st ) => this.GroupEnumerator.BeginGroupItemCount( context, groupPath, cb, st ),
            ( par, iar ) =>
            {
              dataItem.ItemCount = this.GroupEnumerator.EndGroupItemCount( iar );

              if( ( includeItems ) && ( dataItem.ItemCount != null ) && ( !dataItem.ItemCount.IsEstimate ) )
                return new DependentCallResult( null, includeItemsCall, includeItemsCallback );

              contextCalls.MoveNext();

              return contextCalls.Current;
            } ) );
      }

      // A precise item count is required
      includeItemsCall =
        ( cb, st ) =>
        {
          int itemCount = Convert.ToInt32( dataItem.ItemCount.Value );

          SortPosition startPosition = m_groupHelper.CreateSortPosition( context, groupPath, StartPosition.Before );
          JumpMoveAndFetchParameters parameters = new JumpMoveAndFetchParameters( startPosition, 0, itemCount, false, context );

          return this.GroupEnumerator.BeginGetBaseItems( parameters, cb, st );
        };

      includeItemsCallback =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<DataPath> itemsResult = this.GroupEnumerator.EndGetBaseItems( ( JumpMoveAndFetchAsyncResult )iar );

          if( ( m_disposed ) || ( itemsResult == null ) )
          {
            dataItem.Items = null;
          }
          else
          {
            dataItem.Items = itemsResult.FetchedItems;
          }

          contextCalls.MoveNext();

          return contextCalls.Current;
        };

      if( statFunctions != null )
      {
        StatParameters parameters = new StatParameters( statFunctions, groupPath, context );

        contextCallsList.Add(
          new DependentCallResult(
            null,
            ( cb, st ) => this.BeginGetStatValues( parameters, cb, st ),
            ( par, iar ) =>
            {
              StatResults results = this.EndGetStatValues( iar );
              string[] resultPropertyNames = parameters.StatFunctions.Select( ( func ) => func.ResultPropertyName ).ToArray();
              IEnumerable<StatResult> statResults;

              if( ( m_disposed ) || ( results == null ) )
              {
                statResults = Enumerable.Repeat<StatResult>( null, resultPropertyNames.Length );
              }
              else
              {
                statResults = results.Results;
              }

              dataItem.StatContext = new StatContext( resultPropertyNames, statResults );

              contextCalls.MoveNext();

              return contextCalls.Current;
            } ) );
      }

      //Final Call
      contextCallsList.Add(
        new DependentCallResult(
          null,
          ( cb, st ) =>
          {
            AsyncResult endResult = new AsyncResult( Guid.Empty, cb, st );

            endResult.StartSync( null );

            return endResult;
          },
          ( par, iar ) =>
          {
            return this.HandleGroupContextsCallback( context, result, fetchedItems, currentDepth );
          } ) );

      contextCalls = contextCallsList.GetEnumerator();
      contextCalls.MoveNext();

      return contextCalls.Current;
    }

    private bool MoveToNextGroup(
      ref IEnumerator<DataPath> fetchedItems,
      ref int currentDepth )
    {
      currentDepth++;
      DataPath currentPath = fetchedItems.Current;

      while( currentDepth > currentPath.Depth
        || !( currentPath[ currentDepth - 1 ] is GroupDataItem ) )
      {
        if( !fetchedItems.MoveNext() )
          return false;

        currentPath = fetchedItems.Current;
        currentDepth = 1;
      }

      return true;
    }

    private JumpMoveAndFetchAsyncResult BaseBeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    private JumpMoveAndFetchResult<DataPath> BaseEndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult jumpMoveAndFetchAsyncResult )
    {
      return base.EndJumpMoveAndFetch( jumpMoveAndFetchAsyncResult );
    }

    #region Private Fields

    private readonly IContextualGroupHelper m_groupHelper;
    private bool m_disposed; //false

    #endregion
  }
}
